package com.example.bigwork.service.impl;

import com.example.bigwork.dto.request.OrderCreateRequest;
import com.example.bigwork.dto.request.OrderItemRequest;
import com.example.bigwork.dto.response.OrderItemResponse;
import com.example.bigwork.dto.response.OrderResponse;
import com.example.bigwork.entity.Order;
import com.example.bigwork.entity.OrderItem;
import com.example.bigwork.entity.Product;
import com.example.bigwork.entity.User;
import com.example.bigwork.enums.OrderStatus;
import com.example.bigwork.exception.BadRequestException;
import com.example.bigwork.exception.ResourceNotFoundException;
import com.example.bigwork.mapper.OrderItemMapper;
import com.example.bigwork.mapper.OrderMapper;
import com.example.bigwork.mapper.ProductMapper;
import com.example.bigwork.service.OrderService;
import com.example.bigwork.service.ProductService;
import com.example.bigwork.service.UserService;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class OrderServiceImpl implements OrderService {

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;
    private final ProductService productService; // Use service to get product details and check stock
    private final UserService userService; // To validate user exists
    private final ProductMapper productMapper; // For direct stock update

    @Autowired
    public OrderServiceImpl(OrderMapper orderMapper, OrderItemMapper orderItemMapper,
                            ProductService productService, UserService userService, ProductMapper productMapper) {
        this.orderMapper = orderMapper;
        this.orderItemMapper = orderItemMapper;
        this.productService = productService;
        this.userService = userService;
        this.productMapper = productMapper;
    }

    @Override
    @Transactional
    public OrderResponse createOrder(OrderCreateRequest createRequest) {
        // 1. Validate user
        User user = userService.findUserEntityById(createRequest.getUserId());

        Order order = new Order();
        order.setUserId(user.getId());
        order.setOrderNumber(UUID.randomUUID().toString().replace("-", "").substring(0, 16)); // Generate unique order number
        order.setStatus(OrderStatus.PENDING_PAYMENT);
        order.setCreatedAt(LocalDateTime.now());
        order.setUpdatedAt(LocalDateTime.now());

        List<OrderItem> orderItems = new ArrayList<>();
        BigDecimal totalPrice = BigDecimal.ZERO;

        // 2. Process order items
        for (OrderItemRequest itemRequest : createRequest.getItems()) {
            Product product = productService.findProductEntityById(itemRequest.getProductId());

            if (product.getStockQuantity() < itemRequest.getQuantity()) {
                throw new BadRequestException("Insufficient stock for product: " + product.getName() + ". Available: " + product.getStockQuantity());
            }

            OrderItem orderItem = new OrderItem();
            orderItem.setProductId(product.getId());
            orderItem.setQuantity(itemRequest.getQuantity());
            orderItem.setPriceAtPurchase(product.getPrice()); // Lock price at time of purchase
            orderItem.setCreatedAt(LocalDateTime.now());
            orderItems.add(orderItem);

            totalPrice = totalPrice.add(product.getPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity())));

            // 3. Decrease stock (important: do this within the transaction)
            int newStock = product.getStockQuantity() - itemRequest.getQuantity();
            productMapper.updateProductStock(product.getId(), newStock);
        }

        order.setTotalPrice(totalPrice);

        // 4. Save order
        orderMapper.insertOrder(order); // This should set the generated ID back to 'order' object

        // 5. Save order items, linking them to the order
        for (OrderItem item : orderItems) {
            item.setOrderId(order.getId());
        }
        orderItemMapper.insertOrderItems(orderItems); // Batch insert

        order.setItems(orderItems); // Set items for the response DTO mapping
        return mapOrderToOrderResponse(order, user);
    }

    @Override
    public OrderResponse getOrderByOrderNumber(String orderNumber) {
        Order order = orderMapper.findByOrderNumber(orderNumber)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with order number: " + orderNumber));
        return enrichAndMapOrderResponse(order);
    }

    @Override
    public OrderResponse getOrderById(Long orderId) {
        Order order = orderMapper.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with ID: " + orderId));
        return enrichAndMapOrderResponse(order);
    }


    @Override
    public List<OrderResponse> getOrdersByUserId(Long userId) {
        // Validate user exists
        userService.findUserEntityById(userId);
        List<Order> orders = orderMapper.findByUserId(userId);
        return orders.stream()
                .map(this::enrichAndMapOrderResponse)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional
    public OrderResponse updateOrderStatus(String orderNumber, OrderStatus newStatus) {
        Order order = orderMapper.findByOrderNumber(orderNumber)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with order number: " + orderNumber));

        // Basic validation for status transition (can be more complex)
        if (order.getStatus() == OrderStatus.COMPLETED || order.getStatus() == OrderStatus.CANCELLED) {
            throw new BadRequestException("Order is already " + order.getStatus() + " and cannot be updated.");
        }
        // Add more specific transition logic if needed (e.g., cannot ship if not paid)
        if (newStatus == OrderStatus.SHIPPED && order.getStatus() != OrderStatus.PAID) {
            throw new BadRequestException("Order must be PAID before it can be SHIPPED.");
        }


        order.setStatus(newStatus);
        order.setUpdatedAt(LocalDateTime.now());
        orderMapper.updateOrderStatus(order.getId(), newStatus);

        return enrichAndMapOrderResponse(order);
    }

    private OrderResponse enrichAndMapOrderResponse(Order order) {
        User user = userService.findUserEntityById(order.getUserId());
        List<OrderItem> items = orderItemMapper.findByOrderId(order.getId());

        // Enrich order items with product details for the response
        for(OrderItem item : items) {
            Product product = productService.findProductEntityById(item.getProductId());
            item.setProduct(product); // Attach full product for mapping
        }
        order.setItems(items);
        return mapOrderToOrderResponse(order, user);
    }


    private OrderResponse mapOrderToOrderResponse(Order order, User user) {
        OrderResponse response = new OrderResponse();
        BeanUtils.copyProperties(order, response);
        response.setUsername(user.getUsername()); // Add username

        if (order.getItems() != null) {
            response.setItems(order.getItems().stream().map(item -> {
                OrderItemResponse itemResponse = new OrderItemResponse();
                itemResponse.setProductId(item.getProductId());

                // If product is already loaded in OrderItem entity (e.g. from enrichAndMapOrderResponse)
                if (item.getProduct() != null) {
                    itemResponse.setProductName(item.getProduct().getName());
                } else {
                    // Fallback if product details weren't pre-loaded for some reason (e.g. fresh order creation)
                    Product productDetails = productService.findProductEntityById(item.getProductId());
                    itemResponse.setProductName(productDetails.getName());
                }

                itemResponse.setQuantity(item.getQuantity());
                itemResponse.setPriceAtPurchase(item.getPriceAtPurchase());
                itemResponse.setSubtotal(item.getPriceAtPurchase().multiply(BigDecimal.valueOf(item.getQuantity())));
                return itemResponse;
            }).collect(Collectors.toList()));
        }
        return response;
    }
}

